Skip to content

6.1. Containers

What must the image provide?

  • The locked agent package and runtime dependencies.
  • Immutable seed data at /app/data.
  • A writable mount point at /app/state.
  • A non-root UID/GID 10001 process.
  • python -m agent.server as its entrypoint.
  • A2A discovery/task service on 8080.
  • Message-content telemetry capture disabled by default.

No provider credential, development dependency, mutable local database, or Ollama model belongs in the image.

How is the image built reproducibly?

The multi-stage Dockerfile pins uv, the Python build image, and the minimal Wolfi runtime by digest. The build stage uses the locked project without development dependencies; the final stage installs exact Python 3.13 and C++ runtime packages and copies only the virtual environment, seed, and empty state directory:

RUN uv sync --frozen --no-dev --no-install-project
COPY python/README.md ./README.md
COPY python/src ./src
RUN uv sync --frozen --no-dev --no-editable
RUN ln -sfn /usr/bin/python3 /app/.venv/bin/python \
 && install -d -o 10001 -g 10001 /app/state

FROM cgr.dev/chainguard/wolfi-base@sha256:...
RUN apk add --no-cache libstdc++=16.1.0-r4 python-3.13=3.13.14-r2
COPY --from=build /app/.venv /app/.venv
COPY --chown=10001:10001 data /app/data
COPY --from=build --chown=10001:10001 /app/state /app/state
USER 10001:10001
EXPOSE 8080
ENTRYPOINT ["python", "-m", "agent.server"]

The exact apk pins buy reproducibility at a known cost: Wolfi is a rolling repository that drops superseded package versions, so a pinned python-3.13=... or libstdc++=... will eventually stop resolving until Renovate bumps it. If a build suddenly fails with an apk "no such package" error, that is expected drift, not a broken course — refresh the pins to the current versions (or merge the Renovate PR) and rebuild.

The build context is agents/, not agents/python/, because data/ is a sibling.

How do you build and inspect it locally?

From the repository root:

docker build -f agents/python/Dockerfile -t agentops-agent:dev agents
docker image inspect agentops-agent:dev \
  --format '{{.Config.User}} {{json .Config.ExposedPorts}} {{json .Config.Entrypoint}}'

Expected: UID/GID 10001, 8080/tcp, and the Python module entrypoint.

How is runtime state persisted?

The image contains a read-only seed. Kubernetes mounts the agentops-agent-state 1 Gi ReadWriteOnce PVC at /app/state; ADK sessions, A2A tasks, the writable incident copy, and audit rows survive process/pod replacement while the volume exists.

Single-replica SQLite is intentional. Scaling the pod above one requires a shared database and concurrency/migration design, not only changing replicas.

How do you scan the image?

trivy image --severity HIGH,CRITICAL --exit-code 1 agentops-agent:dev

Scan the exact commit-tagged/pushed artifact again in CI or the registry. A clean source dependency audit does not cover OS packages or image configuration.

What is an SBOM for?

A Software Bill of Materials is a machine-readable inventory of everything inside an artifact: OS packages, Python distributions, and their exact versions. It lets you answer supply-chain questions after release — "does any shipped image contain this newly disclosed CVE?" — without rebuilding or guessing, and it supports license review and incident response.

The release workflow generates an SPDX JSON SBOM per image with syft, attaches it to the image in GHCR as a cosign attestation, and uploads it as a GitHub release asset. Both tools (cosign and syft) are Apache-2.0 OSS.

How do I prove an image is the one CI built?

Tagged releases push both images to ghcr.io/mlops-courses/agentops-open-course/agent and .../mlflow and sign them keyless with cosign: GitHub OIDC binds a short-lived certificate to the exact workflow file and tag, so there is no signing key to leak. Verify with public tooling by asserting that identity:

cosign verify \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity "https://github.com/MLOps-Courses/agentops-open-course/.github/workflows/release.yml@refs/tags/v0.1.0" \
  ghcr.io/mlops-courses/agentops-open-course/agent:v0.1.0

cosign verify-attestation --type spdxjson \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity "https://github.com/MLOps-Courses/agentops-open-course/.github/workflows/release.yml@refs/tags/v0.1.0" \
  ghcr.io/mlops-courses/agentops-open-course/agent:v0.1.0

A successful verification proves the artifact was built by that workflow at that tag, and the attestation carries its SBOM. When consuming a published image, pin it by the verified digest (ghcr.io/...@sha256:...), not the tag. Local builds remain the default learning path; referencing published digests from the kustomize overlays is an option, not a requirement.

What is the image checkpoint?

Build, inspect, and scan the image. Then run it only with explicit model/gateway endpoints and a writable state volume; confirm it does not attempt to modify /app/data or run as root.